package com.blustreaksw.cyclecoach.data;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;

public class UserProfile extends SQLiteOpenHelper {
	
	public static final String EXTRA_MEASUREMENT_UNITS =  
			UserProfile.class.getPackage().getName() + ".extra_measurement_units";
	public static final String EXTRA_USER_ID =  
			UserProfile.class.getPackage().getName() + ".extra_user_id";
	public static final String EXTRA_USER_NAME =  
			UserProfile.class.getPackage().getName() + ".extra_user_name";
	public static final String EXTRA_USER_EMAIL =  
			UserProfile.class.getPackage().getName() + ".extra_user_email";
	public static final String EXTRA_USER_WEIGHT =  
			UserProfile.class.getPackage().getName() + ".extra_user_weight";
	public static final String EXTRA_USER_HEIGHT =  
			UserProfile.class.getPackage().getName() + ".extra_user_height";
	public static final String EXTRA_DATE_OF_BIRTH =  
			UserProfile.class.getPackage().getName() + ".extra_date_of_birth";
	public static final String EXTRA_HR_RESTING =  
			UserProfile.class.getPackage().getName() + ".extra_hr_resting";
	public static final String EXTRA_HR_MAX =  
			UserProfile.class.getPackage().getName() + ".extra_hr_max";
	public static final String EXTRA_GENDER =  
			UserProfile.class.getPackage().getName() + ".extra_gender";
	public static final String EXTRA_HRM_NAME =  
			UserProfile.class.getPackage().getName() + ".extra_hrm_name";
	public static final String EXTRA_HRM_ADDRESS =  
			UserProfile.class.getPackage().getName() + ".extra_hrm_address";
	
	
	
	private static final String TAG = "UserProfile";
	
	public enum MeasurementUnits {
		ENGLISH(0), METRIC(1), UNKNOWN(2);
		
		private int value;
		
		private MeasurementUnits (int v) {
			value = v;
		}
		
		/**************************************************************
		 * Retrieves integer value of the enum.
		 * @returns
		 * 	MeasurementUnits numeric value.
		 **************************************************************/
		public int getValue() {
			return value;
		}
		
		/**************************************************************
		 * Converts integer to MeasurementUnits enum object.
		 * @param	value
		 * 	Integer value to be translated to MeasurementUnits.
		 * @return	
		 * 	MeasurementUnits enum object that corresponds to integer
		 *  value.
		 **************************************************************/
		public static MeasurementUnits fromInt(int value) {
			MeasurementUnits status = intToUnitsMap.get(Integer.valueOf(value));
			if(status == null) {
				return MeasurementUnits.UNKNOWN;
			}
			
			return status;
		}
		
		@SuppressLint("UseSparseArrays")
		private static final Map<Integer, MeasurementUnits> intToUnitsMap = new HashMap<Integer, MeasurementUnits>();
		static {
			for(MeasurementUnits status : MeasurementUnits.values()) {
				intToUnitsMap.put(status.value, status);
			}
		}
		
		/**************************************************************
		 * Returns a string representation of the enum.
		 * @return	User readable string.
		 **************************************************************/
		public String toString() {
			switch(this) {
			case ENGLISH:
				return new String("English");
			case METRIC:
				return new String("Metric");
			default:
				return new String("Unknown");
			}
		}
	};
	
	public enum Gender {
		GENDER_MALE(0),
		GENDER_FEMALE(1),
		UNKNOWN(2);
		
		private int value;
		
		private Gender (int v) {
			value = v;
		}
		
		/**************************************************************
		 * Retrieves integer value of the enum.
		 * @returns
		 * 	Gender numeric value.
		 **************************************************************/
		public int getValue() {
			return value;
		}
		
		/**************************************************************
		 * Converts integer to Gender enum object.
		 * @param	value
		 * 	Integer value to be translated to Gender.
		 * @return	
		 * 	Gender enum object that corresponds to integer
		 *  value.
		 **************************************************************/
		public static Gender fromInt(int value) {
			Gender g = intToGenderMap.get(Integer.valueOf(value));
			if(g == null) {
				return Gender.UNKNOWN;
			}
			
			return g;
		}
		
		@SuppressLint("UseSparseArrays")
		private static final Map<Integer, Gender> intToGenderMap = new HashMap<Integer, Gender>();
		static {
			for(Gender g : Gender.values()) {
				intToGenderMap.put(g.value, g);
			}
		}
		
		
		/**************************************************************
		 * Returns a string representation of the enum.
		 * @return	User readable string.
		 **************************************************************/
		public String toString() {
			switch(this) {
			case GENDER_MALE:
				return new String("Male");
			case GENDER_FEMALE:
				return new String("Female");
			default:
				return new String("Unknown");
			}
		}
	};
	
	private class HeartRateZones {
		private int zone1;
		private int zone2;
		private int zone3;
		private int zone4;
		private int zone5;
		
		public HeartRateZones() { 
			zone1 = 0;
			zone2 = 0;
			zone3 = 0;
			zone4 = 0;
			zone5 = 0;
		}
		
		public HeartRateZones(
				int z1, int z2, int z3, int z4, int z5) {
			zone1 = z1;
			zone2 = z2;
			zone3 = z3;
			zone4 = z4;
			zone5 = z5;
		}
		
		public ArrayList<Integer> getValues() {
			ArrayList<Integer> list = new ArrayList<Integer>(5);
			list.add(zone1);
			list.add(zone2);
			list.add(zone3);
			list.add(zone4);
			list.add(zone5);
			
			return list;
		}
		
		public void setZones(
				int z1, int z2, int z3, int z4, int z5) {
			zone1 = z1;
			zone2 = z2;
			zone3 = z3;
			zone4 = z4;
			zone5 = z5;
		}
		
		public void setZones(ArrayList<Integer> list) {
			zone1 = list.get(0);
			zone2 = list.get(1);
			zone3 = list.get(2);
			zone4 = list.get(3);
			zone5 = list.get(4);
		}
	};
	
	/*
	 * UserProfile private data members
	 */
	
	private MeasurementUnits mDisplayUnits;
	private long mId;
	private String mUserName;
	private String mUserEmail;
	private double mWeight;
	private double mHeight;
	private int mAge;
	private Calendar mDateOfBirth;
	private int mRestingHeartRate;
	private int mMaxHeartRate;
	private int mFunctionalThresholdHeartRate;
	private HeartRateZones mRunningZones;
	private HeartRateZones mCyclingZones;
	private int mFunctionalThresholdPower;
	private Gender mGender;
	
	private String mUserName_runkeeper;
	private String mPassword_runkeeper;
	private String mUserName_trainingpeaks;
	private String mPassword_trainingpeaks;
	private String mKey_dropbox;
	private String mSecret_dropbox;
	
	private String mHeartRateMonitorName;
	private String mHeartRateMonitorAddress;
	private String mCyclingSpeedCadenceSensorName;
	private String mCyclingSpeedCadenceSensorAddress;
	private String mCyclingPowerMeterName;
	private String mCyclingPowerMeterAddress;
	
	private double mCycleWheelCircumference;

	public MeasurementUnits getDisplayUnits() {
		return mDisplayUnits;
	}
	
	public void setDisplayUnits(MeasurementUnits mu) {
		mDisplayUnits = mu;
	}

	public long getId() {
		return mId;
	}

	private void setId(long id) {
		mId = id;
	}

	public String getUserName() {
		return mUserName;
	}

	public void setUserName(String userName) {
		mUserName = userName;
	}
	
	public String getUserEmail() {
		return mUserEmail;
	}
	
	public void setUserEmail(String email) {
		mUserEmail = email;
	}

	public int getWeight() {
		if(mDisplayUnits.equals(MeasurementUnits.ENGLISH)){
			return (int) Math.ceil(mWeight * 2.2);
			
		} else {
			return (int) Math.floor(mWeight);
		}
	}
	
	public double getWeightVal() { return mWeight; }

	public void setWeight(int weight) {
		if(mDisplayUnits.equals(MeasurementUnits.ENGLISH)){
			mWeight = ((double) weight) / 2.2;
			
		} else {
			mWeight = weight;
		}
		
		
	}

	public int getHeight() {
		if(mDisplayUnits.equals(MeasurementUnits.ENGLISH)){
			return (int) Math.ceil(mHeight * 0.39);
			
		} else {
			return (int) Math.floor(mHeight);
		}
	}
	
	public double getHeightVal() { return mHeight; }

	public void setHeight(int height) {
		if(mDisplayUnits.equals(MeasurementUnits.ENGLISH)){
			mHeight = ((double) height) * 2.54;
		} else {
			mHeight = height;
		}
	}

	public Calendar getDateOfBirth() {
		return mDateOfBirth;
	}
	
	public String getDateOfBirthString() {
		return new String( 
				mDateOfBirth.getDisplayName(
						Calendar.MONTH, Calendar.LONG, Locale.getDefault())
				+ " " + mDateOfBirth.get(Calendar.DAY_OF_MONTH) 
				+ ", " + mDateOfBirth.get(Calendar.YEAR));
	}
	
	public void setDateOfBirth(Date d) {
		Calendar c = Calendar.getInstance();
		c.setTime(d);
		setDateOfBirth(c);
	}
	
	public void setDateOfBirth(Calendar c) {
		setDateOfBirth(
				c.get(Calendar.YEAR),
				c.get(Calendar.MONTH),
				c.get(Calendar.DAY_OF_MONTH));
	}

	public void setDateOfBirth(int year, int month, int dayOfMonth) {
		mDateOfBirth = Calendar.getInstance();
		mDateOfBirth.set(year, month, dayOfMonth);
		
		mAge = 0;
		
		if(mDateOfBirth != null) {
			Calendar today = Calendar.getInstance();
			today.setTime(new Date());
			
			if(mDateOfBirth.after(today)) {
				return;
			}
			
			mAge = today.get(Calendar.YEAR) - mDateOfBirth.get(Calendar.YEAR);
			
			if(today.get(Calendar.DAY_OF_YEAR) < mDateOfBirth.get(Calendar.DAY_OF_YEAR)) {
				mAge--;
			}
		}
	}
	
	public int getAge() {
		return mAge;
	}

	public int getRestingHeartRate() {
		return mRestingHeartRate;
	}

	public void setRestingHeartRate(int restingHeartRate) {
		mRestingHeartRate = restingHeartRate;
	}

	public int getMaxHeartRate() {
		return mMaxHeartRate;
	}

	public void setMaxHeartRate(int maxHeartRate) {
		mMaxHeartRate = maxHeartRate;
	}
	
	public int getFunctionalThresholdHeartRate() {
		return mFunctionalThresholdHeartRate;
	}
	
	public void setFunctionalThresholdHeartRate(int functionalThresholdHeartRate) {
		mFunctionalThresholdHeartRate = functionalThresholdHeartRate;
	}

	public int getFunctionalThresholdPower() {
		return mFunctionalThresholdPower;
	}

	public void setFunctionalThresholdPower(int functionalThresholdPower) {
		mFunctionalThresholdPower = functionalThresholdPower;
	}

	public Gender getGender() {
		return mGender;
	}

	public void setGender(Gender gender) {
		mGender = gender;
	}
	
	public String getUserName_runkeeper() {
		return mUserName_runkeeper;
	}

	public void setUserName_runkeeper(String userName_runkeeper) {
		mUserName_runkeeper = userName_runkeeper;
	}

	public String getPassword_runkeeper() {
		return mPassword_runkeeper;
	}

	public void setPassword_runkeeper(String password_runkeeper) {
		mPassword_runkeeper = password_runkeeper;
	}

	public String getUserName_trainingpeaks() {
		return mUserName_trainingpeaks;
	}
	
	public void setUserName_trainingpeaks(String userName_trainingpeaks) {
		mUserName_trainingpeaks = userName_trainingpeaks;
	}

	public String getPassword_trainingpeaks() {
		return mPassword_trainingpeaks;
	}

	public void setPassword_trainingpeaks(String password_trainingpeaks) {
		mPassword_trainingpeaks = password_trainingpeaks;
	}
	
	public String getKey_dropbox() {
		return mKey_dropbox;
	}
	
	public void setKey_dropbox(String userName_dropbox) {
		mKey_dropbox = userName_dropbox;
	}
	
	public String getSecret_dropbox() {
		return mSecret_dropbox;
	}

	public void setSecret_dropbox(String password_dropbox) {
		mSecret_dropbox = password_dropbox;
	}
	
	public String getHeartRateMonitorName() {
		return mHeartRateMonitorName;
	}

	public void setHeartRateMonitorName(String heartRateMonitorName) {
		mHeartRateMonitorName = heartRateMonitorName;
	}

	public String getHeartRateMonitorAddress() {
		return mHeartRateMonitorAddress;
	}

	public void setHeartRateMonitorAddress(String heartRateMonitorAddress) {
		mHeartRateMonitorAddress = heartRateMonitorAddress;
	}

	public String getCyclingSpeedCadenceSensorName() {
		return mCyclingSpeedCadenceSensorName;
	}

	public void setCyclingSpeedCadenceSensorName(
			String cyclingSpeedCadenceSensorName) {
		mCyclingSpeedCadenceSensorName = cyclingSpeedCadenceSensorName;
	}

	public String getCyclingSpeedCadenceSensorAddress() {
		return mCyclingSpeedCadenceSensorAddress;
	}

	public void setCyclingSpeedCadenceSensorAddress(
			String cyclingSpeedCadenceSensorAddress) {
		mCyclingSpeedCadenceSensorAddress = cyclingSpeedCadenceSensorAddress;
	}

	public String getCyclingPowerMeterName() {
		return mCyclingPowerMeterName;
	}

	public void setCyclingPowerMeterName(String cyclingPowerMeterName) {
		mCyclingPowerMeterName = cyclingPowerMeterName;
	}

	public String getCyclingPowerMeterAddress() {
		return mCyclingPowerMeterAddress;
	}

	public void setCyclingPowerMeterAddress(String cyclingPowerMeterAddress) {
		mCyclingPowerMeterAddress = cyclingPowerMeterAddress;
	}
	
	public double getCycleWheelCircumference() {
		return mCycleWheelCircumference;
	}

	public void setCycleWheelCircumference(double cycleWheelCircumference) {
		mCycleWheelCircumference = cycleWheelCircumference;
	}
	
	public ArrayList<Integer> getRunningZones() {
		return mRunningZones.getValues();
	}

	public ArrayList<Integer> getCyclingZones() {
		return mCyclingZones.getValues();
	}
	
	
	
	public void autoConfigureRunningHRZones() {
		// TODO
	}
	
	public void autoConfigureCyclingHRZones() {
		// TODO
	}
	
	public void configureRunningHRZones(ArrayList<Integer> list) {
		// TODO
	}
	
	public void configureCyclingHRZones(ArrayList<Integer> list) {
		// TODO
	}	
	
	/***************************************************************
	 * SQLiteOpenHelper functionality for persistent storage
	 * of multiple UserProfile objects
	 ***************************************************************/
	
	/*****************************************************************
	 *****************************************************************
	 * Database "CycleCoach" --- START
	 *****************************************************************
	 *****************************************************************/

	private static final String DB_NAME = "CycleCoachUsers.sqlite";
	private static final int VERSION = 1;
	
	////////////////////////////////////////////////////////////////////
	// Table "users"
	////////////////////////////////////////////////////////////////////
	private static final String TABLE_USERS = "users";
	
	/*****************************************************************
	 * Start Schema for Table "users"
	 *****************************************************************/
	
	// Primary key
	// "_id" auto increments
	// *** Utilized to identify specific users and tie workout
	// *** sessions to a user profile
	private static final String COLUMN_USER_ID_AUTO_INDEX = "_id";
	
	// Display units in METRIC or ENGLISH, stored as String/varchar(10)
	private static final String COLUMN_DISPLAY_UNITS = "displayUnits";
	
	// Friendly user name, stored as String/varchar(100)
	private static final String COLUMN_USER_NAME = "userName";
	
	// User provided email address, stored as String/varchar(100)
	private static final String COLUMN_USER_EMAIL = "userEmail";
	
	// User weight in kilograms, stored as double/real
	private static final String COLUMN_WEIGHT = "weight";
	
	// User height in centimeters, stored as double/real
	private static final String COLUMN_HEIGHT = "height";
	
	// User date of birth, stored as Calendar/int-int-int
	private static final String COLUMN_DOB_YEAR = "dateOfBirthYear";
	private static final String COLUMN_DOB_MONTH = "dateOfBirthMonth";
	private static final String COLUMN_DOB_DAY = "dateOfBirthDay";
	
	// User resting heart rate in beats per minute
	// Stored as int/integer
	private static final String COLUMN_RESTING_HEART_RATE = "restingHeartRate";
	
	// User maximum heart rate in beats per minute
	// Stored as int/integer
	private static final String COLUMN_MAX_HEART_RATE = "maxHeartRate";
	
	// User functional threshold heart rate in beats per minute
	// Stored as int/integer
	private static final String COLUMN_FTHR = "functionalThresholdHeartRate";
	
	// User heart rate zones in beats per minute
	// Auto calculated or specified by user, stored as int/integer
	private static final String COLUMN_RUNNING_ZONE1 = "runningHrZone1";
	private static final String COLUMN_RUNNING_ZONE2 = "runningHrZone2";
	private static final String COLUMN_RUNNING_ZONE3 = "runningHrZone3";
	private static final String COLUMN_RUNNING_ZONE4 = "runningHrZone4";
	private static final String COLUMN_RUNNING_ZONE5 = "runningHrZone5";
	private static final String COLUMN_CYCLING_ZONE1 = "cyclingHrZone1";
	private static final String COLUMN_CYCLING_ZONE2 = "cyclingHrZone2";
	private static final String COLUMN_CYCLING_ZONE3 = "cyclingHrZone3";
	private static final String COLUMN_CYCLING_ZONE4 = "cyclingHrZone4";
	private static final String COLUMN_CYCLING_ZONE5 = "cyclingHrZone5";
	
	// User functional threshold power in watts
	private static final String COLUMN_FTP = "functionalThresholdPower";
	
	// User gender - MALE or FEMALE - stored as String/varchar(10)
	private static final String COLUMN_GENDER = "gender";
	
	// Username/password for Runkeeper.com API, stored as String/varchar(100)
	private static final String COLUMN_RUNKEEPER_ID = "runkeeperUserName";
	private static final String COLUMN_RUNKEEPER_PWD = "runkeeperPassword";
	
	// Username/password for TrainingPeaks.com API, stored as String/varchar(100)
	private static final String COLUMN_TRAININGPEAKS_ID = "trainingpeaksUserName";
	private static final String COLUMN_TRAININGPEAKS_PWD = "trainingpeaksPassword";
	
	// Username/password for Dropbox.com API, stored as String/varchar(100)
	private static final String COLUMN_DROPBOX_ID = "dropboxUserName";
	private static final String COLUMN_DROPBOX_PWD = "dropboxPassword";
	
	// Manufacturer/product name for heart rate monitor
	// Stored as String/varchar(100)
	private static final String COLUMN_HRM_NAME = "heartRateMonitorName";
	
	// Bluetooth Network address for heart rate monitor
	// Stored as String/varchar(100)
	private static final String COLUMN_HRM_ADDRESS = "heartRateMonitorAddress";
	
	// Manufacturer/product name for cycling speed and cadence sensor
	// Stored as String/varchar(100)
	private static final String COLUMN_CSC_NAME = "cyclingSpeedCadenceSensorName";
	
	// Bluetooth Network address for cycling speed and cadence sensor
	// Stored as String/varchar(100)
	private static final String COLUMN_CSC_ADDRESS = "cyclingSpeedCadenceSensorAddress";
	
	// Manufacturer/product name for cycling power meter
	// Stored as String/varchar(100)
	private static final String COLUMN_POW_METER_NAME = "cyclingPowerMeterName";
	
	// Bluetooth Network address for cycling power meter
	// Stored as String/varchar(100)
	private static final String COLUMN_POW_METER_ADDRESS = "cyclingPowerMeterAddress";
	
	// User specified bicycle wheel circumference, in meters
	// Stored as double/real
	private static final String COLUMN_WHEEL_CIRCUMFERENCE = "wheelCircumference";
	
	/*****************************************************************
	 * End Schema for Table "users"
	 *****************************************************************/
	
	/*****************************************************************
	 *****************************************************************
	 * Database "CycleCoach" --- END
	 *****************************************************************
	 *****************************************************************/
	
	private static UserProfile sUserProfile;
	
	public static UserProfile getInstance(Context context) {
		if(sUserProfile == null) {
			sUserProfile = new UserProfile(context);
		}
		
		return sUserProfile;
	}
	
	// Constructor
	private UserProfile(Context context) {
		super(context, DB_NAME, null, VERSION);
		mRunningZones = new HeartRateZones();
		mCyclingZones = new HeartRateZones();
		setDisplayUnits(MeasurementUnits.METRIC);
		mDateOfBirth = Calendar.getInstance();
		mDateOfBirth.setTime(new Date());
		mGender = Gender.UNKNOWN;
		mHeartRateMonitorName = new String("");
		mHeartRateMonitorAddress = new String("");
		mCyclingSpeedCadenceSensorName = new String("");
		mCyclingSpeedCadenceSensorAddress = new String("");
		mCyclingPowerMeterName = new String("");
		mCyclingPowerMeterAddress = new String("");
	}
	
	public void resetProfile(){
		mDisplayUnits = MeasurementUnits.UNKNOWN;
		mId = -1;
		mUserName = new String("");
		mUserEmail = new String("");
		mWeight = 0.0;
		mHeight = 0.0;
		mAge = 0;
		mDateOfBirth = Calendar.getInstance();
		mDateOfBirth.setTime(new Date());
		mRestingHeartRate = 75;
		mMaxHeartRate = 220;
		mFunctionalThresholdHeartRate = 0;
		mRunningZones = new HeartRateZones();
		mCyclingZones = new HeartRateZones();
		mFunctionalThresholdPower = 0;
		mGender = Gender.UNKNOWN;
		setDisplayUnits(MeasurementUnits.METRIC);
		
		mUserName_runkeeper = new String("");
		mPassword_runkeeper = new String("");
		mUserName_trainingpeaks = new String("");
		mPassword_trainingpeaks = new String("");
		mKey_dropbox = new String("");
		mSecret_dropbox = new String("");
		
		mHeartRateMonitorName = new String("");
		mHeartRateMonitorAddress = new String("");
		mCyclingSpeedCadenceSensorName = new String("");
		mCyclingSpeedCadenceSensorAddress = new String("");
		mCyclingPowerMeterName = new String("");
		mCyclingPowerMeterAddress = new String("");
		
		mCycleWheelCircumference = 0.0;
	}
	
	@Override
	public void onCreate(SQLiteDatabase db) {
		// Create the user table
		db.execSQL("create table " + TABLE_USERS + " (" +
				COLUMN_USER_ID_AUTO_INDEX + " integer primary key autoincrement, " +
				COLUMN_DISPLAY_UNITS + " varchar(10), " +
				COLUMN_USER_NAME + " varchar(100), " +
				COLUMN_USER_EMAIL + " varchar(100), " +
				COLUMN_WEIGHT + " real, " +
				COLUMN_HEIGHT + " real, " +
				COLUMN_DOB_YEAR + " integer, " +
				COLUMN_DOB_MONTH + " integer, " +
				COLUMN_DOB_DAY + " integer, " +
				COLUMN_RESTING_HEART_RATE + " integer, " +
				COLUMN_MAX_HEART_RATE + " integer, " +
				COLUMN_FTHR + " integer, " +
				COLUMN_RUNNING_ZONE1 + " integer, " +
				COLUMN_RUNNING_ZONE2 + " integer, " +
				COLUMN_RUNNING_ZONE3 + " integer, " +
				COLUMN_RUNNING_ZONE4 + " integer, " +
				COLUMN_RUNNING_ZONE5 + " integer, " +
				COLUMN_CYCLING_ZONE1 + " integer, " +
				COLUMN_CYCLING_ZONE2 + " integer, " +
				COLUMN_CYCLING_ZONE3 + " integer, " +
				COLUMN_CYCLING_ZONE4 + " integer, " +
				COLUMN_CYCLING_ZONE5 + " integer, " +
				COLUMN_FTP + " integer, " +
				COLUMN_GENDER + " varchar(10), " +
				COLUMN_RUNKEEPER_ID + " varchar(100), " +
				COLUMN_RUNKEEPER_PWD + " varchar(100), " +
				COLUMN_TRAININGPEAKS_ID + " varchar(100), " +
				COLUMN_TRAININGPEAKS_PWD + " varchar(100), " +
				COLUMN_DROPBOX_ID + " varchar(100), " +
				COLUMN_DROPBOX_PWD + " varchar(100), " +
				COLUMN_HRM_NAME + " varchar(100), " +
				COLUMN_HRM_ADDRESS + " varchar(20), " +
				COLUMN_CSC_NAME + " varchar(100), " +
				COLUMN_CSC_ADDRESS + " varchar(20), " +
				COLUMN_POW_METER_NAME + " varchar(100), " +
				COLUMN_POW_METER_ADDRESS + " varchar(20)," +
				COLUMN_WHEEL_CIRCUMFERENCE + " real)");
	}

	@Override
	public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
		// TODO Auto-generated method stub
		
	}
	
	/*
	 * packageUserProfilesForDb()
	 * 
	 * Creates a ContentValues object that contains all of the
	 * values from the UserProfile object that we can store in
	 * the database.
	 * 
	 * Returns:
	 * 		ContentValues object.
	 */
	
	private ContentValues packageUserProfileForDb() {
		ContentValues cv = new ContentValues();
		cv.put(COLUMN_DISPLAY_UNITS, this.getDisplayUnits().toString());
		cv.put(COLUMN_USER_NAME, this.getUserName());
		cv.put(COLUMN_USER_EMAIL, this.getUserEmail());
		cv.put(COLUMN_WEIGHT, this.mWeight);
		cv.put(COLUMN_HEIGHT, this.mHeight);
		cv.put(COLUMN_DOB_YEAR, this.getDateOfBirth().get(Calendar.YEAR));
		cv.put(COLUMN_DOB_MONTH, this.getDateOfBirth().get(Calendar.MONTH));
		cv.put(COLUMN_DOB_DAY, this.getDateOfBirth().get(Calendar.DAY_OF_MONTH));
		cv.put(COLUMN_RESTING_HEART_RATE, this.getRestingHeartRate());
		cv.put(COLUMN_MAX_HEART_RATE, this.getMaxHeartRate());
		cv.put(COLUMN_FTHR, this.getFunctionalThresholdHeartRate());
		
		ArrayList<Integer> runningZones = this.getRunningZones();
		cv.put(COLUMN_RUNNING_ZONE1, runningZones.get(0));
		cv.put(COLUMN_RUNNING_ZONE2, runningZones.get(1));
		cv.put(COLUMN_RUNNING_ZONE3, runningZones.get(2));
		cv.put(COLUMN_RUNNING_ZONE4, runningZones.get(3));
		cv.put(COLUMN_RUNNING_ZONE5, runningZones.get(4));
		
		ArrayList<Integer> cyclingZones = this.getCyclingZones();
		cv.put(COLUMN_CYCLING_ZONE1, cyclingZones.get(0));
		cv.put(COLUMN_CYCLING_ZONE2, cyclingZones.get(1));
		cv.put(COLUMN_CYCLING_ZONE3, cyclingZones.get(2));
		cv.put(COLUMN_CYCLING_ZONE4, cyclingZones.get(3));
		cv.put(COLUMN_CYCLING_ZONE5, cyclingZones.get(4));
		
		cv.put(COLUMN_FTP, this.getFunctionalThresholdPower());
		cv.put(COLUMN_GENDER, this.getGender().toString());
		cv.put(COLUMN_RUNKEEPER_ID, this.getUserName_runkeeper());
		cv.put(COLUMN_RUNKEEPER_PWD, this.getPassword_runkeeper());
		cv.put(COLUMN_TRAININGPEAKS_ID, this.getUserName_trainingpeaks());
		cv.put(COLUMN_TRAININGPEAKS_PWD, this.getPassword_trainingpeaks());
		cv.put(COLUMN_DROPBOX_ID, this.getKey_dropbox());
		cv.put(COLUMN_DROPBOX_PWD, this.getSecret_dropbox());
		cv.put(COLUMN_HRM_NAME, this.getHeartRateMonitorName());
		cv.put(COLUMN_HRM_ADDRESS, this.getHeartRateMonitorAddress());
		cv.put(COLUMN_CSC_NAME, this.getCyclingSpeedCadenceSensorName());
		cv.put(COLUMN_CSC_ADDRESS, this.getCyclingSpeedCadenceSensorAddress());
		cv.put(COLUMN_POW_METER_NAME, this.getCyclingPowerMeterName());
		cv.put(COLUMN_POW_METER_ADDRESS, this.getCyclingPowerMeterAddress());
		cv.put(COLUMN_WHEEL_CIRCUMFERENCE, this.getCycleWheelCircumference());
		
		return cv;
	}
	
	/*
	 * insertNewUserFromCurrentProfile()
	 * 
	 * Takes the data currently in the UserProfile object and
	 * inserts it into the users database. Value returned
	 * by the insert call is the user ID assigned by the database
	 * and is saved in the userId member variable.
	 * 
	 * Returns: long
	 * 		TRUE if database insert successful
	 * 		FALSE otherwise
	 */
	
	public boolean insertNewUserFromCurrentProfile(Context context) {
		
		if(mUserName == null) return false;
		
		if(!verifyUserNameUnique(mUserName)) {
			Log.d(TAG, String.format("%s already exists in db", mUserName));
			return false;
		}
		
		ContentValues cv = packageUserProfileForDb();
		long newUserId = getWritableDatabase().insert(TABLE_USERS, null, cv);
		
		if(newUserId != -1) {
			this.setId(newUserId);
			return true;
		}
		
		return false;
	}
	
	/*
	 * updateUserFromCurrentProfileValues()
	 * 
	 * Updates the users table with the current values
	 * stored in the UserProfile object.
	 * 
	 * Returns: boolean
	 * 		TRUE if success
	 * 		FALSE otherwise
	 */
	public boolean updateUserFromCurrentProfileValues(Context context) {
		
		if(!verifyUserNameUnique(mUserName)) {
			Log.d(TAG, String.format("%s already exists in db", mUserName));
			return false;
		}
		
		ContentValues cv = packageUserProfileForDb();
		
		int rowsAffected = getWritableDatabase().update(
				TABLE_USERS,
				cv,
				COLUMN_USER_ID_AUTO_INDEX + " = ?",
				new String[] {String.valueOf(this.getId()) } );
		
		if(rowsAffected == 1) {
			return true;
		} else {
			return false;
		}
	}
	
	
	/*
	 *  loadUserFromDb()
	 *  
	 *  Sends query to users table to grab all stored data 
	 *  for the specified user ID, saves data in UserProfile.
	 *  
	 *  Returns: boolean
	 *  	TRUE if user exists
	 *  	FALSE otherwise
	 */
	public boolean loadUserFromDb(long id) {
		Cursor c = getReadableDatabase().query( 
				TABLE_USERS, // table
				null, // columns
				COLUMN_USER_ID_AUTO_INDEX + " = ?", // selection
				new String[] { String.valueOf(id) }, // selectionArgs
				null, // groupBy
				null, // having
				null, // orderBy
				"1"); // limit
		
		if(c == null || c.getCount() == 0) return false;
		
		CursorWrapper cw = new CursorWrapper(c);
		cw.moveToFirst();
		
		// mId
		this.setId(id);
		
		// mDisplayUnits
		String displayUnits = cw.getString(cw.getColumnIndex(COLUMN_DISPLAY_UNITS));
		if(displayUnits.equals(MeasurementUnits.ENGLISH.toString())) {
			this.setDisplayUnits(MeasurementUnits.ENGLISH);
		} else {
			this.setDisplayUnits(MeasurementUnits.METRIC);
		}
		
		// mUserName
		this.setUserName(cw.getString(cw.getColumnIndex(COLUMN_USER_NAME)));
		
		// mUserEmail
		this.setUserEmail(cw.getString(cw.getColumnIndex(COLUMN_USER_EMAIL)));
		
		// mWeight
		this.mWeight = cw.getDouble(cw.getColumnIndex(COLUMN_WEIGHT));
		
		// mHeight
		this.mHeight = cw.getDouble(cw.getColumnIndex(COLUMN_HEIGHT));
		
		// mDateOfBirth
		this.setDateOfBirth(
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_YEAR)),
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_MONTH)),
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_DAY)));
		
		// mRestingHeartRate
		this.setRestingHeartRate(cw.getInt(cw.getColumnIndex(COLUMN_RESTING_HEART_RATE)));
		
		// mMaxHeartRate
		this.setMaxHeartRate(cw.getInt(cw.getColumnIndex(COLUMN_MAX_HEART_RATE)));
		
		// mFunctionalThresholdHeartRate
		this.setFunctionalThresholdHeartRate(cw.getInt(cw.getColumnIndex(COLUMN_FTHR)));
		
		// mFunctionalThresholdPower
		this.setFunctionalThresholdPower(cw.getInt(cw.getColumnIndex(COLUMN_FTP)));
		
		// mGender
		String gender = cw.getString(cw.getColumnIndex(COLUMN_GENDER));
		if(gender.equals(Gender.GENDER_MALE.toString())) {
			this.setGender(Gender.GENDER_MALE);
		} else {
			this.setGender(Gender.GENDER_FEMALE);
		}
		
		// mUserName_runkeeper
		this.setUserName_runkeeper(cw.getString(cw.getColumnIndex(COLUMN_RUNKEEPER_ID)));
		
		// mPassword_runkeeper
		this.setPassword_runkeeper(cw.getString(cw.getColumnIndex(COLUMN_RUNKEEPER_PWD)));
		
		// mUserName_trainingpeaks
		this.setKey_dropbox(cw.getString(cw.getColumnIndex(COLUMN_TRAININGPEAKS_ID)));
		
		// mPassword_trainingpeaks
		this.setPassword_trainingpeaks(cw.getString(cw.getColumnIndex(COLUMN_TRAININGPEAKS_PWD)));
		
		// mUserName_dropbox
		this.setKey_dropbox(cw.getString(cw.getColumnIndex(COLUMN_DROPBOX_ID)));
		
		// mPassword_dropbox
		this.setSecret_dropbox(cw.getString(cw.getColumnIndex(COLUMN_DROPBOX_PWD)));
		
		// mHeartRateMonitorName
		this.setHeartRateMonitorName(cw.getString(cw.getColumnIndex(COLUMN_HRM_NAME)));
		
		// mHeartRateMonitorAddress
		this.setHeartRateMonitorAddress(cw.getString(cw.getColumnIndex(COLUMN_HRM_ADDRESS)));
		
		// mCyclingSpeedCadenceSensorName
		this.setCyclingSpeedCadenceSensorName(cw.getString(cw.getColumnIndex(COLUMN_CSC_NAME)));
		
		// mCyclingSpeedCadenceSensorAddress
		this.setCyclingSpeedCadenceSensorAddress(cw.getString(cw.getColumnIndex(COLUMN_CSC_ADDRESS)));
		
		// mCyclingPowerMeterName
		this.setCyclingPowerMeterName(cw.getString(cw.getColumnIndex(COLUMN_POW_METER_NAME)));
		
		// mCyclingPowerMeterAddress
		this.setCyclingPowerMeterAddress(cw.getString(cw.getColumnIndex(COLUMN_POW_METER_ADDRESS)));
		
		// mCycleWheelCircumference
		this.setCycleWheelCircumference(cw.getFloat(cw.getColumnIndex(COLUMN_WHEEL_CIRCUMFERENCE)));
		
		// mRunningZones
		this.mRunningZones = new HeartRateZones(
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE1)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE2)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE3)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE4)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE5)));
		
		// mCyclingZones
		this.mCyclingZones = new HeartRateZones(
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE1)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE2)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE3)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE4)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE5)));
		
		cw.close();
		return true;
	}
	
	public void dumpUserInfoToLogCat(long id) {
		Cursor c = getReadableDatabase().query( 
				TABLE_USERS, // table
				null, // columns
				COLUMN_USER_ID_AUTO_INDEX + " = ?", // selection
				new String[] { String.valueOf(id) }, // selectionArgs
				null, // groupBy
				null, // having
				null, // orderBy
				"1"); // limit
		
		if(c == null || c.getCount() == 0) return;
		
		CursorWrapper cw = new CursorWrapper(c);
		cw.moveToFirst();
		
		// mId
		Log.d(TAG, String.format("userId: %d", id));
		
		// mDisplayUnits
		Log.d(TAG, String.format("units: %s", cw.getString(cw.getColumnIndex(COLUMN_DISPLAY_UNITS))));
				
		// mUserName
		Log.d(TAG, String.format("name: %s", cw.getString(cw.getColumnIndex(COLUMN_USER_NAME))));
		
		// mUserEmail
		Log.d(TAG, String.format("email: %s", cw.getString(cw.getColumnIndex(COLUMN_USER_EMAIL))));
		
		// mWeight
		Log.d(TAG, String.format("weight: %1.2f", cw.getDouble(cw.getColumnIndex(COLUMN_WEIGHT))));
		
		// mHeight
		Log.d(TAG, String.format("height: %1.2f", cw.getDouble(cw.getColumnIndex(COLUMN_HEIGHT))));
		
		// mDateOfBirth
		Log.d(TAG, String.format("dob: %d-%d-%d", 
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_MONTH)) + 1,
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_DAY)),
				cw.getInt(cw.getColumnIndex(COLUMN_DOB_YEAR))));

		// mRestingHeartRate
		Log.d(TAG, String.format("rest HR: %d", cw.getInt(cw.getColumnIndex(COLUMN_RESTING_HEART_RATE))));
		
		// mMaxHeartRate
		Log.d(TAG, String.format("max HR: %d", cw.getInt(cw.getColumnIndex(COLUMN_MAX_HEART_RATE))));
		
		// mFunctionalThresholdHeartRate
		Log.d(TAG, String.format("functional threshold HR: %d", cw.getInt(cw.getColumnIndex(COLUMN_FTHR))));
		
		// mFunctionalThresholdPower
		//this.setFunctionalThresholdPower(cw.getInt(cw.getColumnIndex(COLUMN_FTP)));
		
		// mGender
		Log.d(TAG, String.format("Gender: %s", cw.getString(cw.getColumnIndex(COLUMN_GENDER))));

		// mUserName_runkeeper
		//this.setUserName_runkeeper(cw.getString(cw.getColumnIndex(COLUMN_RUNKEEPER_ID)));
		
		// mPassword_runkeeper
		//this.setPassword_runkeeper(cw.getString(cw.getColumnIndex(COLUMN_RUNKEEPER_PWD)));
		
		// mUserName_trainingpeaks
		//this.setKey_dropbox(cw.getString(cw.getColumnIndex(COLUMN_TRAININGPEAKS_ID)));
		
		// mPassword_trainingpeaks
		//this.setPassword_trainingpeaks(cw.getString(cw.getColumnIndex(COLUMN_TRAININGPEAKS_PWD)));
		
		// mUserName_dropbox
		Log.d(TAG, String.format("dropbox key: %s", cw.getString(cw.getColumnIndex(COLUMN_DROPBOX_ID))));
		
		// mPassword_dropbox
		Log.d(TAG, String.format("dropbox secret: %s", cw.getString(cw.getColumnIndex(COLUMN_DROPBOX_PWD))));
		
		// mHeartRateMonitorName
		Log.d(TAG, String.format("HRM name: %s", cw.getString(cw.getColumnIndex(COLUMN_HRM_NAME))));
		
		// mHeartRateMonitorAddress
		Log.d(TAG, String.format("HRM address: %s", cw.getString(cw.getColumnIndex(COLUMN_HRM_ADDRESS))));
		
		// mCyclingSpeedCadenceSensorName
		//this.setCyclingSpeedCadenceSensorName(cw.getString(cw.getColumnIndex(COLUMN_CSC_NAME)));
		
		// mCyclingSpeedCadenceSensorAddress
		//this.setCyclingSpeedCadenceSensorAddress(cw.getString(cw.getColumnIndex(COLUMN_CSC_ADDRESS)));
		
		// mCyclingPowerMeterName
		//this.setCyclingPowerMeterName(cw.getString(cw.getColumnIndex(COLUMN_POW_METER_NAME)));
		
		// mCyclingPowerMeterAddress
		//this.setCyclingPowerMeterAddress(cw.getString(cw.getColumnIndex(COLUMN_POW_METER_ADDRESS)));
		
		// mCycleWheelCircumference
		//this.setCycleWheelCircumference(cw.getFloat(cw.getColumnIndex(COLUMN_WHEEL_CIRCUMFERENCE)));
		
		// mRunningZones
		/*this.mRunningZones = new HeartRateZones(
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE1)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE2)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE3)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE4)),
				cw.getInt(cw.getColumnIndex(COLUMN_RUNNING_ZONE5)));
		
		// mCyclingZones
		this.mCyclingZones = new HeartRateZones(
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE1)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE2)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE3)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE4)),
				cw.getInt(cw.getColumnIndex(COLUMN_CYCLING_ZONE5)));*/
		
		cw.close();
	}
	
	/*
	 * deleteUser()
	 * 
	 * Deletes the specified userId. Gets user confirmation with an
	 * AlertDialog prompt.
	 * 
	 * Returns: boolean
	 * 		FALSE if no user with that ID
	 * 		TRUE otherwise
	 * 		
	 */
	public boolean deleteUser(final long userId) {
		
		// First check to see if the user exists.
		Cursor c = getReadableDatabase().query(
				TABLE_USERS, // table
				null, // columns
				COLUMN_USER_ID_AUTO_INDEX + " = ?", // selection
				new String[] { String.valueOf(userId) }, // selectionArgs
				null, // groupBy
				null, // having
				null, // orderBy
				"1"); // limit
		
		if(c == null || (c.getCount() == 0)) return false;
		CursorWrapper cw = new CursorWrapper(c);
		cw.moveToFirst();
		final String userName = cw.getString(cw.getColumnIndex(COLUMN_USER_NAME));
		cw.close();
		
		int rowsAffected = getWritableDatabase().delete(
				TABLE_USERS,
				COLUMN_USER_ID_AUTO_INDEX + " = ?", 
				new String[] { String.valueOf(userId) });
		
		if(rowsAffected == 1) {
			Log.d(TAG, String.format("deleteUser() userName %s removed from db", userName));
			return true;
		}
		
		return false;
	}
	
	/* 
	 * getUserList()
	 * 
	 * Query the users table and create a list of all users
	 * that contains their name and user ID.
	 * 
	 * Returns: boolean
	 * 		TRUE if items were added to lists passed by reference
	 * 		FALSE otherwise
	 */
	public boolean getUserList(ArrayList<UserNameAndIdContainer> userList) {
		
		final String[] columns = { COLUMN_USER_ID_AUTO_INDEX, COLUMN_USER_NAME };
		int rowCount = 0;
		
		Cursor c = getReadableDatabase().query( 
				TABLE_USERS, // table
				columns, // columns
				null, // selection
				null, // selectionArgs
				null, // groupBy
				null, // having
				COLUMN_USER_NAME, // orderBy
				null); // limit
		
		if(c == null || (rowCount = c.getCount()) == 0) return false;
		
		CursorWrapper cw = new CursorWrapper(c);
		cw.moveToFirst();
		
		int userIdColumnIndex = cw.getColumnIndex(COLUMN_USER_ID_AUTO_INDEX);
		int userNameColumnIndex = cw.getColumnIndex(COLUMN_USER_NAME);
		
		for(int i = 0; i < rowCount; i++) {
			UserNameAndIdContainer uc =
					new UserNameAndIdContainer(
							cw.getString(userNameColumnIndex), cw.getLong(userIdColumnIndex));
			userList.add(uc);
			cw.moveToNext();
		}
		
		cw.close();
		return true;
	}
	
	/*
	 * verifyUserNameUnique()
	 * 
	 * Checks the list of users to verify that the name submitted is
	 * not already in use.
	 * 
	 * Returns: boolean
	 * 		TRUE if no string match
	 * 		FALSE otherwise
	 */
	public boolean verifyUserNameUnique(String name) {
		ArrayList<UserNameAndIdContainer> userList = new ArrayList<UserNameAndIdContainer>();
		
		if(!getUserList(userList)) return true;
		
		int count = userList.size();
		for(int i = 0; i < count; i++) {
			if(userList.get(i).getUserName().equals(name) && 
					userList.get(i).getUserId() != getId()) {
				return false;
			}
		}
		
		return true;
	}
	
	
	public final static boolean isValidEmail(CharSequence target) {
		if(target == null) {
			return false;
		} else {
			return android.util.Patterns.EMAIL_ADDRESS.matcher(target).matches();
		}
	}
	
	// User names can contain uppercase/lowercase letters or numbers and must be
	// between 2 and 20 characters.
	private static final Pattern USER_NAME_PATTERN = Pattern.compile("[A-z][A-z0-9]{1,20}");
	
	public final static boolean isValidUsername(CharSequence target) {
		if(target == null) {
			return false;
		} else {
			return USER_NAME_PATTERN.matcher(target).matches();
		}
	}
	
	/**********************************************************************
	 * Validates weight passed by user. Returns
	 **********************************************************************/
	public final boolean isValidWeight(int weight) {

		if(getDisplayUnits().equals(MeasurementUnits.ENGLISH)) {
			// minimum 22 lbs, max 550 lbs
			if(weight < 22.0 || weight > 550.0) {
				return false;
			}
		} else if(getDisplayUnits().equals(MeasurementUnits.METRIC)) {
			// minimum 10 kg, max 250 lbs
			if(weight < 22.0 || weight > 250.0) {
				return false;
			}
		} else {
			return false;
		}
		
		return true;
	}
	
	/**********************************************************************
	 * Validates height passed by user. Returns
	 **********************************************************************/
	public final boolean isValidHeight(int height) {

		if(getDisplayUnits().equals(MeasurementUnits.ENGLISH)) {
			// minimum 24 inches, max 108 inches
			if(height < 24.0 || height > 108.0) {
				return false;
			}
		} else if(getDisplayUnits().equals(MeasurementUnits.METRIC)) {
			// minimum 61 cm, max 275 cm
			if(height < 22.0 || height > 275.0) {
				return false;
			}
		} else {
			return false;
		}
		
		return true;
	}
	
	
	public class UserNameAndIdContainer {
		private String mUserName;
		private long mUserId;
		
		UserNameAndIdContainer (String userName, long userId) {
			mUserName = userName;
			mUserId = userId;
		}
		
		public String getUserName() { return mUserName; }
		public long getUserId() { return mUserId; }
	}
	
}